//
// Created by tang on 23-3-12.
//

#include "pi_config_service.hpp"
#include <cstdint>
#include <fstream>
#include <concepts>
#include <json/json.hpp>
#include <map>
#include <type_traits>
#include "core/assert.hpp"
#include "imgui.h"
#include "imgui_ext/ImGuiUtils.h"
#include "imgui_ext/imgui_ext.h"
#include "imgui_stdlib.h"
#include "utils/io_tools.hpp"
#include "utils/safe_value.hpp"

struct BasicItem {
    virtual ~BasicItem () = default;

    std::string caption;
    std::string help;

    virtual bool is_holder() const { return false; }
    virtual bool is_group() const { return false; }
    virtual bool is_same(void* value) { (void)value; return false; }
    virtual void load () = 0;
    virtual void store () = 0;
    virtual void draw ()  = 0;
    virtual void assign(const std::string &v) {}
    virtual std::string to_string() { return {}; }
};

template <typename T>
    requires copiable<T>
struct BasicReadWriteItem: BasicItem {
    safe_value<T>  holder;
    T              backup;

    BasicReadWriteItem(T v): holder(v), backup{v} {

    }

    void load() final override {
        backup = holder.read();
    }

    void store() final override {
        holder.write(backup);
    }

    bool is_same(void *value) override { return &holder == value; }
};

template <typename T>
    requires std::is_floating_point_v<T> || std::is_integral_v<T>
struct InputItem : BasicReadWriteItem<T> {
    ImGuiDataType       data_type{};
    ImGuiInputTextFlags flags{0};

    using BasicReadWriteItem<T>::BasicReadWriteItem;

    void draw () override {
        std::string id = "##" + this->caption;
        ImGui::TextUnformatted (this->caption.c_str());
        ImGui::SameLine (ImGui::GetFontSize() * 16.f);
        ImGui::SetNextItemWidth (ImGui::GetFontSize() * 36.f);
        ImGui::InputScalar(id.c_str(), this->data_type, &this->backup, NULL, NULL, NULL, flags);
        if (!this->help.empty()) {
            ImGui::TextHelperSign(this->help.c_str());
        }
    }

    void assign(const std::string &v) override {
        switch (data_type)
        {
        case ImGuiDataType_U8:
        case ImGuiDataType_U16:
        case ImGuiDataType_U32:
        case ImGuiDataType_U64: this->backup = (T)std::stoull (v); this->holder.write (this->backup);
            break;
        case ImGuiDataType_S8:
        case ImGuiDataType_S16:
        case ImGuiDataType_S32:
        case ImGuiDataType_S64: this->backup = (T)std::stoll (v); this->holder.write (this->backup);
            break;
        case ImGuiDataType_Float:
        case ImGuiDataType_Double: this->backup = (T)std::stod (v); this->holder.write (this->backup);
        }
    }

    std::string to_string() override {
        return std::to_string(this->holder.read());
    }
};

struct StringInputItem : BasicReadWriteItem<std::string> {
    using BasicReadWriteItem<std::string>::BasicReadWriteItem;
    ImGuiInputTextFlags flags{0};

    void draw () override {
        std::string id = "##" + caption;
        ImGui::TextUnformatted (caption.c_str());
        ImGui::SameLine (ImGui::GetFontSize() * 16.f);
        ImGui::SetNextItemWidth (ImGui::GetFontSize() * 36.f);
        ImGui::InputText(id.c_str(), &backup, flags);
        if (!help.empty()) {
            ImGui::TextHelperSign(help.c_str());
        }
    }
    void assign(const std::string &v) override {
        holder.write(v);
    }
    std::string to_string() override {
        return holder.read();
    }
};

template <typename T>
    requires std::is_integral_v<T> || std::is_floating_point_v<T>
struct SliderItem : BasicReadWriteItem<T> {
    using BasicReadWriteItem<T>::BasicReadWriteItem;

    T              min{};
    T              max{};
    ImGuiDataType  data_type{};

    void draw () override {
        std::string id = "##" + this->caption;
        ImGui::TextUnformatted (this->caption.c_str());
        ImGui::SameLine (ImGui::GetFontSize() * 16.f);
        ImGui::SetNextItemWidth (ImGui::GetFontSize() * 36.f);
        ImGui::SliderScalar(id.c_str(), this->data_type, &this->backup, &min, &max);
        if (!this->help.empty()) {
            ImGui::TextHelperSign(this->help.c_str());
        }
    }
    void assign(const std::string &v) override {
        switch (data_type)
        {
        case ImGuiDataType_U8:
        case ImGuiDataType_U16:
        case ImGuiDataType_U32:
        case ImGuiDataType_U64: this->backup = (T)std::stoull (v); this->holder.write (std::clamp(this->backup, min, max));
            break;
        case ImGuiDataType_S8:
        case ImGuiDataType_S16:
        case ImGuiDataType_S32:
        case ImGuiDataType_S64: this->backup = (T)std::stoll (v); this->holder.write (std::clamp(this->backup, min, max));
            break;
        case ImGuiDataType_Float:
        case ImGuiDataType_Double: this->backup = (T)std::stod (v); this->holder.write (std::clamp(this->backup, min, max));
        }
    }
    std::string to_string() override {
        return std::to_string(this->holder.read());
    }
};

struct CheckboxItem : BasicReadWriteItem<bool> {
    using BasicReadWriteItem<bool>::BasicReadWriteItem;

    void draw () override {
        std::string id = "##" + this->caption;
        ImGui::TextUnformatted (this->caption.c_str());
        ImGui::SameLine (ImGui::GetFontSize() * 16.f);
        ImGui::SetNextItemWidth (ImGui::GetFontSize() * 36.f);
        ImGui::Checkbox(id.c_str(), &this->backup);
        if (!this->help.empty()) {
            ImGui::TextHelperSign(this->help.c_str());
        }
    }
    void assign(const std::string &v) override {
        if (!v.empty()) {
            if (v=="true") {
                holder.write(true);
            } else if (v == "false") {
                holder.write(false);
            }
        }
    }
    std::string to_string() override {
        return this->holder.read() ? "true" : "false";
    }
};

struct ToggleItem : CheckboxItem {
    using CheckboxItem::CheckboxItem;

    void draw () override {
        std::string id = "##" + this->caption;
        ImGui::TextUnformatted (this->caption.c_str());
        ImGui::SameLine (ImGui::GetFontSize() * 16.f);
        ImGui::SetNextItemWidth (ImGui::GetFontSize() * 36.f);
        ImGui::ToggleButton(id.c_str(), &this->backup);
        if (!this->help.empty()) {
            ImGui::TextHelperSign(this->help.c_str());
        }
    }
};

template <typename T>
    requires std::is_integral_v<T>
struct SelectItem : BasicReadWriteItem<T> {
    const char *(*get_text) (void *user, int idx){};
    s32   count{};
    void *user{};

    using BasicReadWriteItem<T>::BasicReadWriteItem;

    void draw () override {
        std::string id = "##" + this->caption;
        ImGui::TextUnformatted (this->caption.c_str());
        ImGui::SameLine (ImGui::GetFontSize() * 16.f);
        ImGui::SetNextItemWidth (ImGui::GetFontSize() * 36.f);
        std::string preview = get_text(user, (int)this->backup);
        if (ImGui::BeginCombo(id.c_str(), preview.c_str()))
        {
            for (int i=0; i<count; ++i) {
                std::string tmp = get_text(user, i);
                if (ImGui::Selectable(tmp.c_str(), i==(int)this->backup))
                {
                    this->backup = (T)i;
                }
            }
            ImGui::EndCombo();
        }
        if (!this->help.empty()) {
            ImGui::TextHelperSign(this->help.c_str());
        }
    }
    void assign(const std::string &v) override {
        if (!v.empty()) {
            this->backup = std::stoll(v);
            this->holder.write(this->backup);
        }
    }
    std::string to_string() override {
        return std::to_string(this->holder.read());
    }
};

struct GroupItem: BasicItem {
    using store_type = std::pair<const std::string, BasicItem*>;

    bool draw_self {true};
    std::list<store_type> items;

    void load () override {
        for (auto &x: items) {
            x.second->load();
        }
    }
    void store () override {
        for (auto &x: items) {
            x.second->store();
        }
    }
    void draw () override {
        bool open = !draw_self || ImGui::TreeNodeEx(caption.c_str(), (ImGuiTreeNodeFlags_CollapsingHeader |
                                                               ImGuiTreeNodeFlags_AllowItemOverlap |
                                                               ImGuiTreeNodeFlags_DefaultOpen) & ~ImGuiTreeNodeFlags_NoTreePushOnOpen);
        if (open) {
            for (auto &x: items)
                x.second->draw();
        }

        if (draw_self && open)
            ImGui::TreePop();
    }
    bool is_group() const override { return true; }
};

template <typename T> requires requires { {(value_type)value_traits<T>::store_type} -> std::same_as<value_type>; }
struct DataItem: BasicItem {
    T store_{};

    DataItem(T v): store_(v) {}

    void load () override {}
    void store () override {}
    void draw () override {}

    void assign(const std::string &v) override {
        if constexpr (std::is_floating_point_v<T>)
        {
            double val = std::stod(v);
            store_ = (T)val;
        }
        else if constexpr (std::is_integral_v<T>)
        {
            u64 val = std::stoull(v);
            store_ = (T)val;
        }
        else if constexpr (std::is_same_v<T, std::string>) {
            store_ = v;
        } else {
            // TODO: Support
        }
    }

    std::string to_string() override {
        if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T>)
        {
            return std::to_string (store_);
        }
        else if constexpr (std::is_same_v<T, std::string>) {
            return store_;
        } else {
            return ""; // TODO: Support
        }
    }

    bool is_same(void *value) override { return value == &store_; }
};

struct HolderItem: BasicItem {
    std::string store_value;

    bool is_holder() const override { return true; }

    void load () override {}
    void store () override {}
    void draw () override {}
    std::string to_string() override { return store_value; }
};

static BasicItem* create_input(void *value_holder, value_type vt, void *def, ImGuiInputTextFlags flags = 0)
{
    BasicItem *ret = nullptr;

#define CASE(SDK_VT,IMGUI_DT,DATA_TYPE) \
    case value_type::SDK_VT: { \
        auto item = new InputItem<DATA_TYPE>(*(DATA_TYPE*)def); \
        *static_cast<safe_value<DATA_TYPE>**>(value_holder) = &item->holder; \
        item->data_type = ImGuiDataType_##IMGUI_DT;           \
        item->flags = flags;           \
        ret = item; \
    } break

    switch (vt)
    {
    CASE(U8, U8,u8);
    CASE(U16,U16,u16);
    CASE(U32,U32,u32);
    CASE(U64,U64,u64);
    CASE(S8, S8, s8);
    CASE(S16,S16,s16);
    CASE(S32,S32,s32);
    CASE(S64,S64,s64);
    CASE(F32,Float,f32);
    CASE(F64,Double,f64);
    case value_type::STD_STRING: {
        auto item = new StringInputItem(*(std::string*)def);
        *static_cast<safe_value<std::string>**>(value_holder) = &item->holder;
        item->flags = flags;
        ret = item;
    }
    break;
// TODO: Support
//    case value_type::C_STRING: break;
    default: PI_ASSERT_MSG (0, "Invalid value_type!!"); break;
    }
    return ret;
#undef CASE
}

static BasicItem *create_slider(void *value_holder, value_type vt, void *min, void *max, void* def) {
    BasicItem *ret = nullptr;

#define CASE(SDK_VT,IMGUI_DT,DATA_TYPE) \
    case value_type::SDK_VT: { \
        auto item = new SliderItem<DATA_TYPE>{*(DATA_TYPE*)def}; \
        *static_cast<safe_value<DATA_TYPE>**>(value_holder) = &item->holder; \
        item->data_type = ImGuiDataType_##IMGUI_DT;           \
        item->min = (DATA_TYPE)(intptr_t)min; \
        item->max = (DATA_TYPE)(intptr_t)max; \
        ret = item; \
    } break

    switch(vt) {
        CASE(U8,U8,u8);
        CASE(U16,U16,u16);
        CASE(U32,U32,u32);
        CASE(U64,U64,u64);
        CASE(S8, S8, s8);
        CASE(S16,S16,s16);
        CASE(S32,S32,s32);
        CASE(S64,S64,s64);
        CASE(F32,Float,f32);
        CASE(F64,Double,f64);
        default: PI_ASSERT_MSG (0, "Invalid value_type!!"); break;
    }
    return ret;
#undef CASE
}

static BasicItem *create_select(void *value_holder, value_type vt, const char*(*text)(void*,int), int count, void *user, void *def)
{
    BasicItem *ret = nullptr;

#define CASE(SDK_VT,DATA_TYPE) \
    case value_type::SDK_VT: { \
        auto item = new SelectItem<DATA_TYPE>{*(DATA_TYPE*)def}; \
        *static_cast<safe_value<DATA_TYPE>**>(value_holder) = &item->holder; \
        item->get_text = text; \
        item->count = count; \
        item->user = user; \
        ret = item; \
    } break

    switch(vt) {
        CASE(U8, u8);
        CASE(U16,u16);
        CASE(U32,u32);
        CASE(U64,u64);
        CASE(S8, s8);
        CASE(S16,s16);
        CASE(S32,s32);
        CASE(S64,s64);
        default: PI_ASSERT_MSG (0, "Invalid value_type!!"); break;
    }
    return ret;
#undef CASE
}

static BasicItem *create_data(void *holder, value_type vt, void *def) {
    BasicItem *ret = nullptr;

#define CASE(VALUE_TYPE, DATA_TYPE) \
    case value_type::VALUE_TYPE: {  \
        auto item = new DataItem<DATA_TYPE>{*(DATA_TYPE*)def}; \
        *static_cast<DATA_TYPE**>(holder) = &item->store_; \
        ret = item;                          \
    } break
    switch (vt)
    {
    CASE(U8,  u8);
    CASE(U16, u16);
    CASE(U32, u32);
    CASE(U64, u64);
    CASE(S8,  s8);
    CASE(S16, s16);
    CASE(S32, s32);
    CASE(S64, s64);
    CASE(F32, f32);
    CASE(F64, f64);
    CASE(STD_STRING, std::string);
    // TODO: C_STRING, VEC2, VEC3, VEC4
    default: PI_ASSERT_MSG (0, "UNSUPPORTED value type!!");
    }
    return ret;
#undef CASE
}

class PiConfigGroup final : public IConfigService::IGroup
{
    GroupItem *group_;
public:
    explicit PiConfigGroup(BasicItem *group_item) {
        group_ = dynamic_cast<GroupItem*>(group_item);
        PI_ASSERT (group_);
    }

    std::unique_ptr<IGroup> child_group(const char *key, const std::string &display) override
    {
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type& item) {
        return item.first == key;
        });
        if (it == end(group_->items)) {
            GroupItem *g = new GroupItem;
            g->draw_self = true;
            g->caption = display;
            it = group_->items.emplace (end(group_->items), key, g);
        } else if (it->second->caption.empty()) {
            it->second->caption = display;
        }
        return std::make_unique<PiConfigGroup>(it->second);
    }

protected:
    IGroup *input_impl (const std::string &key, const std::string &caption, void *value_holder, value_type vt, void* def, const std::string &help) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto input = create_input (value_holder, vt, def);
            input->caption = caption;
            input->help = help;
            group_->items.emplace (end(group_->items), key, input);
        } else if (it->second->is_holder()) {
            // replace this
            auto input = create_input (value_holder, vt, def);
            input->caption = caption;
            input->help = help;
            input->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = input;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }

    IGroup *secret_impl (const std::string &key, const std::string &caption, void *value_holder, value_type vt, void* def, const std::string &help) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto input = create_input (value_holder, vt, def, ImGuiInputTextFlags_Password);
            input->caption = caption;
            input->help = help;
            group_->items.emplace (end(group_->items), key, input);
        } else if (it->second->is_holder()) {
            // replace this
            auto input = create_input (value_holder, vt, def, ImGuiInputTextFlags_Password);
            input->caption = caption;
            input->help = help;
            input->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = input;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }

    IGroup *slider_impl (const std::string &key, const std::string &caption, void *value_holder, value_type vt, void *min, void *max, void* def,
                         const std::string &help) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto slider = create_slider (value_holder, vt, min, max, def);
            slider->caption = caption;
            slider->help = help;
            group_->items.emplace (end(group_->items), key, slider);
        } else if (it->second->is_holder()) {
            // replace this
            auto slider = create_slider (value_holder, vt, min, max, def);
            slider->caption = caption;
            slider->help = help;
            slider->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = slider;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }
    IGroup *checkbox_impl (const std::string &key, const std::string &caption, void *value_holder, void* def, const std::string &help) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto checkbox = new CheckboxItem{*(bool*)def};
            *(safe_value<bool>**)value_holder = &checkbox->holder;
            checkbox->caption = caption;
            checkbox->help = help;
            group_->items.emplace (end(group_->items), key, checkbox);
        } else if (it->second->is_holder()) {
            // replace this
            auto checkbox = new CheckboxItem{*(bool*)def};
            *(safe_value<bool>**)value_holder = &checkbox->holder;
            checkbox->caption = caption;
            checkbox->help = help;
            checkbox->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = checkbox;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }
    IGroup *toggle_impl (const std::string &key, const std::string &caption, void *value_holder, void* def, const std::string &help) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto toggle = new ToggleItem{*(bool*)def};
            *(safe_value<bool>**)value_holder = &toggle->holder;
            toggle->caption = caption;
            toggle->help = help;
            group_->items.emplace (end(group_->items), key, toggle);
        } else if (it->second->is_holder()) {
            // replace this
            auto toggle = new ToggleItem{*(bool*)def};
            *(safe_value<bool>**)value_holder = &toggle->holder;
            toggle->caption = caption;
            toggle->help = help;
            toggle->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = toggle;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }
    IGroup *select_impl (const std::string &key, const std::string &caption, void *value_holder, value_type vt, const char *(*text) (void *, int),
                         int count, void *user, void* def, const std::string &help) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto select = create_select (value_holder, vt, text, count, user, def);
            select->caption = caption;
            select->help = help;
            group_->items.emplace (end(group_->items), key, select);
        } else if (it->second->is_holder()) {
            // replace this
            auto select = create_select (value_holder, vt, text, count, user, def);
            select->caption = caption;
            select->help = help;
            select->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = select;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }

    IGroup * data_impl(const std::string &key, void *value_holder, value_type vt, void* def) override
    {
        // find child
        auto it = std::find_if(begin(group_->items), end(group_->items), [key](const GroupItem::store_type &item) {
            return key == item.first;
        });
        if (it == end(group_->items)) {
            // create one
            auto data = create_data (value_holder, vt, def);
            group_->items.emplace (end(group_->items), key, data);
        } else if (it->second->is_holder()) {
            // replace this
            auto data = create_data (value_holder, vt, def);
            data->assign (dynamic_cast<HolderItem*>(it->second)->store_value);
            auto item = it->second; it->second = data;
            delete item;
        } else {
            PI_ASSERT_MSG (it->second->is_same(value_holder), "Conflict Key '%s'??", key.c_str());
        }
        return this;
    }
};

struct ConfigObject {
    GroupItem root;
};

pi_config_service::pi_config_service (std::string cfg_file_path): cfg_file_(cfg_file_path) {
    cfg_obj_ = new ConfigObject;
    cfg_obj_->root.caption = "RootNode";
    cfg_obj_->root.draw_self = false;
    load();
}
pi_config_service::~pi_config_service () {
    save();
    delete cfg_obj_;
}
std::unique_ptr<IConfigService::IGroup> pi_config_service::group (const char *key, const std::string &display)
{
    auto it = std::find_if(begin(cfg_obj_->root.items), end(cfg_obj_->root.items), [key](const GroupItem::store_type& item) {
        return item.first == key;
    });
    if (it == end(cfg_obj_->root.items)) {
        GroupItem *g = new GroupItem;
        g->draw_self = true;
        g->caption = display;
        it = cfg_obj_->root.items.emplace (end(cfg_obj_->root.items), key, g);
    } else if (it->second->caption.empty()) {
        it->second->caption = display;
    }
    return std::make_unique<PiConfigGroup>(it->second);
}
void pi_config_service::render_config_ui () const {
    cfg_obj_->root.draw();
}

static const char enc_key[32] = "PiInfoApp,ConfigFile,Enctrypted";

static std::tuple<std::string, BasicItem*> create_from_json(const nlohmann::json &json) {
    std::string key  = json.value ("__key__", "");
    std::string type = json.value ("__type__", "");

    PI_ASSERT (!key.empty() && !type.empty());

    BasicItem *item = nullptr;

    if (type == "group") {
        item = new GroupItem;
    } else if (type == "item") {
        item = new HolderItem;
    }

    return {key, item};
}

static void deserialize(const nlohmann::json &json, BasicItem *item) {
    std::string type = json.value ("__type__", "");
    std::string key  = json.value ("__key__", "");
    auto values = json["__value__"];
    if (!values.is_null())
    {
        if (type == "group" && item->is_group())
        {
            auto g = dynamic_cast<GroupItem*>(item);
            for (auto &node: values) {
                auto [k,v] = create_from_json (node);
                if (v) {
                    g->items.emplace (end(g->items), k, v);
                    deserialize (node, v);
                }
            }
        }
        else if (type == "item" && item->is_holder())
        {
            auto h = dynamic_cast<HolderItem*>(item);
            values.get_to (h->store_value);
        }
    }
}

struct BinFormat {
    u32   total_length;
    u32   contetn_length;
    char  data[0];

    static BinFormat *from_content(void *ptr, size_t len) {
        auto alloc_len = len + sizeof(BinFormat);
        // 确保是16的倍数
        alloc_len = ((alloc_len-1)/16+1)*16;
        void *mem = malloc (alloc_len);
        BinFormat *ret = new(mem)BinFormat;
        ret->total_length = alloc_len;
        ret->contetn_length = len;
        memcpy (ret->data, ptr, len);
        return ret;
    }
};

void pi_config_service::load () {
    using namespace nlohmann;

    auto content = ReadEncryptedFile(cfg_file_, enc_key);
    if (!content.empty()) {
        // parse content
        BinFormat *dat = (BinFormat*)content.data();
        if (dat->total_length != content.size())
            return; // invalid data

        std::string json_text{dat->data, dat->data + dat->contetn_length};

        try {
            auto doc = json::parse (json_text);
            deserialize (doc, &cfg_obj_->root);
        } catch (std::exception &e) {
            L_ERROR ("config file parse error! %s", e.what());
        }

    }
}

static nlohmann::json serialize(const std::string &key, BasicItem *item) {
    using namespace nlohmann;

    if (item->is_group()) {
        auto obj = json::object ();
        obj["__key__"] = key;
        obj["__type__"] = "group";

        auto *g = dynamic_cast<GroupItem*>(item);
        auto arr = json::array ();
        for (auto &x: g->items) {
            arr.push_back (serialize (x.first, x.second));
        }
        obj["__value__"] = arr;
        return obj;
    } else {
        auto obj = json::object ();
        obj["__key__"] = key;
        obj["__type__"] = "item";
        obj["__value__"] = item->to_string();
        return obj;
    }
}

void pi_config_service::save () {
    using namespace nlohmann;

    // to json string
    auto root = serialize ("__root__", &cfg_obj_->root);
    std::string config_json = root.dump ();

    // L_INFO ("%s", config_json.c_str());

    BinFormat *bin = BinFormat::from_content (config_json.data(), config_json.length());

    // encrypt
    auto const encrypted_content = EncryptContent(bin, bin->total_length, enc_key);

    free(bin);

    // write data to file
    std::ofstream ofs(cfg_file_, std::ios::trunc | std::ios::binary);
    if (ofs.is_open()) {
        ofs.write ((const char*)encrypted_content.data(), encrypted_content.size());
        ofs.close();
    } else {
        L_ERROR ("File %s could not write!", cfg_file_.c_str());
    }
}
void pi_config_service::open () {
    cfg_obj_->root.load();
}
void pi_config_service::close (bool save) {
    if (save) {
        cfg_obj_->root.store();
        this->save();
    }
}
